package com.uxsino.commons.logicSelector;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.uxsino.commons.logicSelector.propFunction.CastPropValueFunction;
import com.uxsino.commons.logicSelector.propFunction.IPropValueFunction;
import com.uxsino.commons.logicSelector.propFunction.PropWildcardValueFunction;

public class SelectorContext<ObjectType> {

    /**
     * object to test against
     */
    private ObjectType object;

    /**
     * stands for the purpose of the searching, can be any thing
     */
    private Object testFor = null;

    private Class<ObjectType> objectClass;

    private Map<String, IPropValueFunction> valueFuncCache;

    private static Map<Class<?>, Map<String, PropOperator>> operators = new HashMap<>();

    // Class of the final class that inherited from this
    private Class<? extends SelectorContext<ObjectType>> thisClass;

    /*
     * acceptCallback: return true/false to override result
     */
    private TriFunction<SelectorContext<?>, ISelector<ObjectType>, Boolean, Boolean> acceptCallback;

    static {
        registerContextType(SelectorContext.class);
        registerOperator(SelectorContext.class, "eq", new GeneralPropOperators.OpEq());
        registerOperator(SelectorContext.class, "lt", new GeneralPropOperators.OpLt());
        registerOperator(SelectorContext.class, "gt", new GeneralPropOperators.OpGt());
        registerOperator(SelectorContext.class, "le", new GeneralPropOperators.OpLe());
        registerOperator(SelectorContext.class, "ge", new GeneralPropOperators.OpGe());
        registerOperator(SelectorContext.class, "ne", new GeneralPropOperators.OpNe());
        registerOperator(SelectorContext.class, "in", new GeneralPropOperators.OpIn());
        registerOperator(SelectorContext.class, "notIn", new GeneralPropOperators.OpNotIn());
        registerOperator(SelectorContext.class, "has", new GeneralPropOperators.OpHas());
        registerOperator(SelectorContext.class, "exists", new GeneralPropOperators.OpExists());
        registerOperator(SelectorContext.class, "reg", new GeneralPropOperators.OpReg());

    }

    @FunctionalInterface
    public static interface TriFunction<T, U, S, R> {

        /**
         * Applies this function to the given arguments.
         *
         * @param t the first function argument
         * @param u the second function argument
         * @param s the third function argument
         * @return the function result
         */
        R apply(T t, U u, S s);
    }

    public SelectorContext(Class<ObjectType> objectClass, Class<? extends SelectorContext<ObjectType>> thisClass) {
        this.objectClass = objectClass;
        this.thisClass = thisClass;
        valueFuncCache = new HashMap<>();
    }

    public void setObject(ObjectType object) {
        this.object = object;
    }

    public ObjectType getObject() {
        return object;
    }

    private IPropValueFunction getValueFunc(String propExpr) {
        if (valueFuncCache.containsKey(propExpr)){
            return valueFuncCache.get(propExpr);
        }

        List<String> l = Arrays.asList(propExpr.split("\\."));

        IPropValueFunction func = createPVF(l, objectClass);
        valueFuncCache.put(propExpr, func);
        return func;
    }

    /**
     * create Property Value Function
     * 
     * @param expr
     * @param cls
     * @return
     */
    private IPropValueFunction createPVF(List<String> expr, Class<?> cls) {

        String parts[] = expr.get(0).trim().split("\\[");
        String propName = parts[0];
        String index = null;
        if (parts.length > 2) {
            throw new IllegalArgumentException("illegal expression in prop name:" + expr.get(0));
        }

        if (parts.length == 2) {
            String index_parts[] = parts[1].trim().split("\\]");

            // "[" not closed
            if (index_parts.length != 1 || index_parts[0].length() == parts[1].length()){
                throw new IllegalArgumentException("\"[]\" doesn't match: " + expr.get(0));
            }

            index = index_parts[0].trim();
        }
        IPropValueFunction baseFunc = PropWildcardValueFunction.creaetValueFunction(propName, index, cls);

        if (expr.size() == 1) {
            return baseFunc;
        }

        return new CastPropValueFunction(baseFunc, createPVF(expr.subList(1, expr.size()), null));
    }

    public Object getValue(String propExpr) {
        IPropValueFunction func = getValueFunc(propExpr);

        if (func != null) {
            return func.get(object);
        }

        return null;
    }

    public void setTestFor(Object obj) {
        testFor = obj;
    }

    public Object getTestFor() {
        return testFor;
    }

    public void setOnAccept(TriFunction<SelectorContext<?>, ISelector<ObjectType>, Boolean, Boolean> func) {
        acceptCallback = func;
    }

    public boolean afterAccept(ISelector<ObjectType> selector, Boolean result) {
        return acceptCallback == null ? result : acceptCallback.apply(this, selector, result);
    }

    public static void registerContextType(Class<?> contextClass) {
        Class<?> cls = contextClass.getSuperclass();
        if (SelectorContext.class == contextClass) {
            operators.put(contextClass, new HashMap<>());
        } else {
            if (!operators.containsKey(cls)) {
                registerContextType(cls);
            }
            operators.put(contextClass, new HashMap<>(operators.get(cls)));
        }
    }

    // must call registerContextType first
    public static synchronized void registerOperator(Class<?> contextClass, String opName, PropOperator op) {
        operators.get(contextClass).put(opName, op);
    }

    public PropOperator getOperator(String opName) {
        return operators.get(thisClass).get(opName);
    }

    public String[] getOperatorNames() {
        return operators.keySet().toArray(new String[operators.keySet().size()]);
    }

}
